package com.atguigu.vip.spring.framework.context;

import com.atguigu.vip.spring.framework.annotation.GPAutowired;
import com.atguigu.vip.spring.framework.annotation.GPController;
import com.atguigu.vip.spring.framework.annotation.GPService;
import com.atguigu.vip.spring.framework.beans.BeanDefinition;
import com.atguigu.vip.spring.framework.beans.BeanPostProcessor;
import com.atguigu.vip.spring.framework.beans.BeanWrapper;
import com.atguigu.vip.spring.framework.context.support.BeanDefinitionReader;
import com.atguigu.vip.spring.framework.core.GPBeanFactory;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/***
 * 这个类 就好比于ClassPathXmlApplicationContext，。。
 *
 * 容器 或者 上下文
 */
public class GPApplicationContext18Bak implements GPBeanFactory {

    private String[] configLocations;
    private BeanDefinitionReader reader;
    //beanDefinitionMap 是用来保存配置信息的
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //而这个map是保存单例bean的一个map，用来保证注册是单例的容器
    private Map<String,Object> beanCacheMap = new HashMap<>();

    //用来存储所有被代理过的对象
    private Map<String,BeanWrapper> beanWrapperMap = new ConcurrentHashMap<>();

    public GPApplicationContext18Bak(String... configLocations) {
        this.configLocations  = configLocations;
        this.refresh();  //configLocations当作参数传给refresh不是用的形参，而是用的成员变量
    }

    //这个方法有个非常重要的方法：refresh
    public void refresh(){

        //定位，应该写在BeanDefinitionReader里面去。。这时候reader里面就有config和registryBeanClasses（其实就是beanDefinitions）了。
        this.reader = new BeanDefinitionReader(configLocations);
        //加载，要拿到所有的配置信息
        //加载是通过reader来加载
        List<String> beanDefinitions = reader.loadBeanDefinitions();    //类的名字，其实就是beanDefinitions
        //注册
        doRegistry(beanDefinitions);


        //依赖注入，这里指的是lazy-init=false的时候，要执行的
        //在这里自动调用getBean()方法
        doAutoWired();

    }



    //开始执行 自动化的依赖注入
    private void doAutoWired() {

        for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();

            //如果不是延迟加载，这里就依赖注入
            if(!beanDefinitionEntry.getValue().isLazyInit()){
               getBean(beanName);
            }

        }

        //解决多次循环问题，这里强制循环？？
        for (Map.Entry<String, BeanWrapper> beannWrapperEntry : beanWrapperMap.entrySet()) {

            populateBean(beannWrapperEntry.getKey(),beannWrapperEntry.getValue().getWrappedInstance());

        }



    }

    //真正依赖注入的方法
    public void populateBean(String beanName,Object instance){
        Class clazz = instance.getClass();

        //不是Controller 和 Service注解的，就不注入
        if( !(clazz.isAnnotationPresent(GPController.class) ||
                clazz.isAnnotationPresent(GPService.class)) ){
            return;
        }
        //否则就注入

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if(!field.isAnnotationPresent(GPAutowired.class)) {
                continue;
            }
            //如果有自定义注解
            GPAutowired autowired = field.getAnnotation(GPAutowired.class);
            String autowiredBeanName = autowired.value().trim();

            if("".equals(autowiredBeanName)){
                autowiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            try {
                field.set(instance,this.beanWrapperMap.get(autowiredBeanName).getWrappedInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }

    }


    //真正的将BeanDefinitions注册到beanDefinitionMap中（这就是spring的容器）
    private void doRegistry(List<String> beanDefinitions) {
        try{
            for (String className : beanDefinitions) {
                //beanName有三种情况：
                //1、默认是类名字母小写
                //2、自定义名字
                //3、接口注入
                Class<?> beanClass = Class.forName(className);
                //如果是一个接口，是不能实例化的。
                //用它的实现类取实例化
                if(beanClass.isInterface()){
                    continue;
                }
                //不是接口的话，就可以通过reader的方法去拿到一个BeanDefinition
                BeanDefinition beanDefinition = reader.registerBean(className);
                if(beanDefinition != null){
                    this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
                }

                Class<?>[] interfaces = beanClass.getInterfaces();
                for (Class<?> i : interfaces) {
                    //如果是多个实现类，只能覆盖。。而且spring会报错。
                    //为什么？因为spring没那么智能，就是这么傻。
                    //这时候可以自定义名字（这里就用@Quality这个注解指定名字）
                    this.beanDefinitionMap.put(i.getName(),beanDefinition);
                }
                //到这里为止，容器初始化完成

            }
        }catch (Exception e){
            e.printStackTrace();
        }


    }

    /**
     * 1、依赖注入是从这里开始的。。它首先要做的是实例的初始化
     *
     * 2、通过读取BeanDefinition中的信息
     * 然后通过反射去创建一个实例并返回
     *
     * 3、spring的做法是：不会把最原始的对象放出去，这里会用一个BeanWrapper进行一次包装，把实例放到BeanWrapper里面去（就是装饰器模式）
     * 为什么要装饰器模式呢？
     * 3.1、保留原来的OOP关系
     * 3.2、需要对它进行扩展，增强（为以后的AOP打基础）
     */
    @Override
    public Object getBean(String beanName) {

        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        String className = beanDefinition.getBeanClassName();

        try{

            //生成通知事件
            BeanPostProcessor beanPostProcessor = new BeanPostProcessor();



//            if(){}  如果是单例，如何如何
            Object instance = instantionBean(beanDefinition);
            if(null == instance){ return null; }

            //在实例初始化之前调用一次
            beanPostProcessor.postProcessBeforeInitialization(instance,beanName);

            BeanWrapper beanWrapper = new BeanWrapper(instance);
            beanWrapper.setPostProcessor(beanPostProcessor);
            this.beanWrapperMap.put(beanName,beanWrapper);

            //在实例初始化之后调用一次，就是通知一下别人，我实例初始化成功了
            beanPostProcessor.postProcessAfterInitialization(instance,beanName);


//            populateBean(beanName,instance); 这里会有多次循环 报错的问题

            //通过这样调用，相当于给我们自己留有了课操作的空间。
            return this.beanWrapperMap.get(beanName).getWrappedInstance();
        }catch (Exception e){
            e.printStackTrace();
        }

        return null;

    }

    //这个方法是创建实例的方法 。。
    //传一个BeanDefinition，返回一个实例bean
    private Object instantionBean(BeanDefinition beanDefinition){
        Object instance = null;
        String className = beanDefinition.getBeanClassName();
        try {
            Class<?> clazz = Class.forName(className);
            //这里默认是单例, 因为根据class才能确定一个类是否有实例。
            //如果这里有实例，则直接返回
            if(this.beanCacheMap.containsKey(className)){
                instance = this.beanCacheMap.get(className);
            }else {
                //如果这里没有实例的话，利用反射得到实例
                instance = Class.forName(className).newInstance();
                this.beanCacheMap.put(className,instance);
            }
            return instance;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Object getBean(Class<?> beanClass) {
        return null;
    }
}
